/*
 * Copyright (C) 2010 TopCoder Inc., All Rights Reserved.
 */
package com.hercules.xre.protocol.testing.netty.tcp;
import java.util.Arrays;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

import com.hercules.xre.protocol.testing.Helper;
import com.hercules.xre.protocol.testing.netty.ExceptionHandlingChannelFutureListener;
import com.topcoder.util.log.Level;
import com.topcoder.util.log.Log;
import com.topcoder.util.log.LogManager;
/**
 * <p>
 * Class responsible for validating if first bytes sent to the application contain
 * required header (as defined in section 2.6 of XRE Protocol Specification).
 * </p>
 * <p>
 * <strong>Thread-safety:</strong> This class is thread safe as it's immutable and it's parent class is thread safe.
 * </p>
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
*/
public class TCPFirstMessageDecoder extends FrameDecoder {
    /**
     * <p>
     * Array of bytes that are expected in the first message sent to the application.
     * These data represent protocol header as defined in section 2.6 of XRE Protocol
     * Specification.
     * </p>
    */
    private static final byte[]  PROTOCOL_HEADER =  {0x58, 0x52, 0x45, 0x0D, 0x0A};

    /**
     * <p>
     * Logger that is used to log activities and errors.
     * It is initialized to LogManager.getLog(TCPFirstMessageDecoder.class.getName()).
     * Can not be null, never changes after initialization.
     * </p>
    */
    private static final Log LOGGER = LogManager.getLog(TCPFirstMessageDecoder.class.getName());

    /**
     * Default constructor, does nothing.
    */
    public TCPFirstMessageDecoder() {
    }

    /**
     * <p>
     * Method responsible for validating first bytes sent to the application.
     * According to section 2.6 of XRE Protocol Specification first 5 bytes
     * that should be sent by the client should contain "XRE" followed by a
     * carriage return and a line feed.
     * </p>
     *
     * <p>
     * If protocol header is recognized correctly then this Decoder removes itself
     * from the pipeline (thus there it won't try to validate any next message).
     * If header is invalid, decoder closes the connection.
     * </p>
     * @param buffer buffer containing received data, can't be null
     * @param ctx channel handler context for handled message, can't be null
     * @param channel channel to which message has been sent, can't be null
     * @return If there is not enough data (<5 bytes) null is returned, if header is invalid null is returned
     *         if header is valid then all bytes after the header are returned (to be passed to next handler)
     * @throws IllegalArgumentException if any argument is invalid
    */
    protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
        final String signature = "TCPFirstMessageDecoder#decode";
        Helper.logEnter(signature, new String[] {"ChannelHandlerContext", "channel", "buffer"},
            new Object[] {ctx, channel, buffer}, LOGGER);
        Helper.checkNull(ctx, "ChannelHandlerContext", signature, LOGGER);
        Helper.checkNull(channel, "channel", signature, LOGGER);
        Helper.checkNull(buffer, "buffer", signature, LOGGER);

        // Make sure if the length field was received.
        if (buffer.readableBytes() < PROTOCOL_HEADER.length) {
            // more data is needed, wait till buffer gets filled
            return Helper.logExit(signature, null, LOGGER);
        }
        // buffer contains required amount of data, check if it's correct
        byte[] messageHeader = new byte[PROTOCOL_HEADER.length];
        buffer.readBytes(messageHeader);
        if (Arrays.equals(messageHeader, PROTOCOL_HEADER)) {
            // message header is appropriate,
            //  - remove first message handler (as it has fulfilled its duty)
            //  - return what's left in the buffer to be handled by next handler in the pipeline
            ChannelPipeline pipeline = ctx.getPipeline();
            // Note : it possible be null
            if (pipeline != null) {
                pipeline.remove(this);
            }
            return Helper.logExit(signature, buffer, LOGGER);
        } else {
            // message header is invalid
            LOGGER.log(Level.ERROR, signature + " : invalid header : "
                + Arrays.toString(messageHeader));
            ChannelFuture future = channel.close();
            future.addListener(new ExceptionHandlingChannelFutureListener());
            // return null to prevent further data propagation
            return Helper.logExit(signature, null, LOGGER);
        }
    }
}

